Skip to content

feat: ZS_Dead equivalent — death XP and loot init on NPC kill#375

Open
BoroBongo wants to merge 7 commits into
mainfrom
feat/zs-dead
Open

feat: ZS_Dead equivalent — death XP and loot init on NPC kill#375
BoroBongo wants to merge 7 commits into
mainfrom
feat/zs-dead

Conversation

@BoroBongo

Copy link
Copy Markdown
Contributor

FightService.OnNpcDied() now replicates Gothic's ZS_Dead AI state logic:

  1. Resets AIV_PLUNDERED to 0 (FALSE) on the dead NPC's instance
  2. If EnableDeathXP and the killer is the hero: calls B_DeathXP via the Daedalus VM (sets XP, level, LP), then syncs results to hero.Vob via NpcService.SyncHeroInstanceToVob()
  3. Always calls B_GiveDeathInv so the loot panel has items to show
  4. Restores GlobalSelf / GlobalOther after both VM calls

Enable DeathXP is gated behind EnableCombatSystem in DeveloperConfig and marked WIP (damage values are still debug stubs).

Also adds EnableLevel5Cheat and EnableGuildCheat flags in DeveloperConfig (Misc / WIP section) used by StatusMenu cheat triggers.

BoroBongo and others added 3 commits June 15, 2026 21:46
FightService.OnNpcDied() now replicates Gothic's ZS_Dead AI state logic:
1. Resets AIV_PLUNDERED to 0 (FALSE) on the dead NPC's instance
2. If EnableDeathXP and the killer is the hero: calls B_DeathXP via the
   Daedalus VM (sets XP, level, LP), then syncs results to hero.Vob via
   NpcService.SyncHeroInstanceToVob()
3. Always calls B_GiveDeathInv so the loot panel has items to show
4. Restores GlobalSelf / GlobalOther after both VM calls

EnableDeathXP is gated behind EnableCombatSystem in DeveloperConfig and
marked WIP (damage values are still debug stubs).

Also adds EnableLevel5Cheat and EnableGuildCheat flags in DeveloperConfig
(Misc / WIP section) used by StatusMenu cheat triggers.
@BoroBongo BoroBongo marked this pull request as ready for review June 15, 2026 22:30
@BoroBongo BoroBongo requested a review from JaXt0r June 17, 2026 11:50
@BoroBongo

Copy link
Copy Markdown
Contributor Author

Tested and now after killing NPC you'll see a change in the status menu. Killed diego and got a lot of levels 🗡️

public bool EnableCombatSystem;

[ConditionalField(fieldToCheck: nameof(EnableCombatSystem), compareValues: true)]
[Tooltip("Call B_DeathXP on kill to grant XP. WIP - XP values may be incorrect.")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the value might be incorrect? Can you add it to be non-WIP? ;-)


[ConditionalField(fieldToCheck: nameof(EnableCombatSystem), compareValues: true)]
[Tooltip("Call B_DeathXP on kill to grant XP. WIP - XP values may be incorrect.")]
public bool EnableDeathXP;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs to be guarded by a feature flag at all. Should do no harm to the game at runtime.

if (target.Props.BodyState == VmGothicEnums.BodyState.BsDead)
return;

Logger.Log($"[FightService.OnHit] *** {attacker.Instance.GetName(NpcNameSlot.Slot0)} HIT {target.Instance.GetName(NpcNameSlot.Slot0)}", LogCat.Npc);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Logger.Log($"[FightService.OnHit] *** {attacker.Instance.GetName(NpcNameSlot.Slot0)} HIT {target.Instance.GetName(NpcNameSlot.Slot0)}", LogCat.Fight);

}
}

var bGiveDeathInv = vm.GetSymbolByName("B_GiveDeathInv");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use existing constants file for it:
Assets/Gothic-Core/Scripts/Const/DaedalusConst.cs

var aivPlundered = vm.GetSymbolByName("AIV_PLUNDERED")?.GetInt(0) ?? 8;
dead.Instance.SetAiVar(aivPlundered, 0);

var oldSelf = vm.GlobalSelf;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. We might have this more often to switch self/other to something different, then switch it back afterwards.
How about having a helper function as ZenKitExtension.cs but like GothicExtension.cs? With a function called ExchangeDaedalus() + ExchangeDaedalueBack() or so? Saves some back and forth code in the future?

private void OnNpcDied(NpcContainer dead, NpcContainer killer)
{
var vm = _gameStateService.GothicVm;
var aivPlundered = vm.GetSymbolByName("AIV_PLUNDERED")?.GetInt(0) ?? 8;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put symbol into any of the *Constants. files.


if (_configService.Dev.EnableDeathXP && killer.PrefabProps.IsHero())
{
var bDeathXp = vm.GetSymbolByName("B_DeathXP");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constants.

vob.Attributes[attributeId] = value;
}

public void SyncHeroInstanceToVob()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Why is it needed now?
I like it. It reminds me of the sync mechanism which is needed for the Save() logic. Why now? Or would it be sufficient to collect the data at save time?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants